//Copyright (C) 2009 Ron Coleman. Contact: ronncoleman@gmail.com
//
//This library is free software; you can redistribute it and/or
//modify it under the terms of the GNU General Public License
//as published by the Free Software Foundation; either
//version 3 of the License, or (at your option) any later version.
//
//This library is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
//General Public License for more details.
//
//You should have received a copy of the GNU General Public License
//along with this library; if not, write to the Free Software
//Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

package org.workplicity.marist.grounds.ui.table;

import org.workplicity.marist.grounds.GroundsRequest;
import org.workplicity.marist.grounds.util.CompositeOrder;
import org.workplicity.marist.grounds.util.GroundsDate;
import org.workplicity.marist.grounds.worklet.GroundsWorklet;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.Hashtable;
import javax.swing.table.AbstractTableModel;
import org.workplicity.entry.User;
import org.workplicity.entry.WorkRequest;
import org.workplicity.entry.WorkSlate;

import org.workplicity.entry.event.Event;
import org.workplicity.entry.event.Once;
import org.workplicity.entry.event.Recurring;
import org.workplicity.marist.grounds.util.GroundsHelper;
import org.workplicity.task.NetTask;
import org.workplicity.util.Helper;

import org.workplicity.util.Logger;
import org.workplicity.util.WorkDate;

/**
 *
 * @author Ron Coleman
 */
public class RequestTableModel extends AbstractTableModel {
    public final static int ID = 0;
    public final static int UPDATED = 1;
    public final static int STATE = 2;
    public final static int PRIORITY = 3;
    public final static int SLATE = 4;
    public final static int WORKREQUIRED = 5;

    private String[] columnNames = {"Id",
                                    "Updated",
                                    "State",
                                    "Priority",
                                    "Slate",
                                    "Work required"};

    private WorkRequest.Status status = WorkRequest.Status.DISPATCHED;
    private Integer serviceId = -1;

    private ArrayList<CompositeOrder> requests = new ArrayList<CompositeOrder>();
    private Hashtable eventsCache = new Hashtable<Integer,Event>( );
    private long start;
    private long end;
    private User user = null;

    public int getRowCount() {
        return requests.size();
    }

    public int getColumnCount() {
        return columnNames.length;
    }

    @Override
    public String getColumnName(int col) {
        return columnNames[col];
    }

    public Object getValueAt(int row, int col) {
        CompositeOrder co = requests.get(row);

        switch(col) {
            case 0:
                return co.getRequest().getId();

            case 1:               
                return new GroundsDate(co.getRequest().getUpdateDate()).toString();

            case 2:
                return getState(co);

            case 3:
               GroundsRequest request = co.getRequest();

               return request.getPriority();
                
            case 4:
                String name = co.getSlateName();

                return name == null ? "---" : name;
                
            case 5:
                return co.getRequest().getDescr();
        }

        return null;
    }

    private final static long DELAY = 5 * 60 * 1000;

    private String getState(CompositeOrder co) {
        GroundsRequest request = co.getRequest();

        Event event = co.getEvent();

        String state = "";

        // Get change status

        // If the user hasn't logged out, everything will appear
        // as "new"
        if(user.getStamp() == null)
            state = "New ";

        else if (user.getStamp().getTime() < request.getUpdateDate().getTime()) {
            Date updateDate = new Date(request.getUpdateDate().getTime());
            Date createDate = new Date(request.getCreateDate().getTime());
            Date firedDate = null;

            // Use the event to tell if this is a recently fired request
            if(event != null) {
                if(event.getLastFired() != null)
                    firedDate = new Date(event.getLastFired().getTime());

                // If the event is coming from the composite request
                // the event must already be in the cache and there's
                // not need to put it there.
//                eventsCache.put(event.getId(), event);
            }
            else if(request.getFutureId() != -1) {
                event = getEvent(request.getFutureId());

                if(event != null)
                    firedDate = event.getLastFired();

                // So we don't leave this as a recurring request itself which
                // it isn't
                event = null;
            }
            
            long dt0 = request.getUpdateDate().getTime() - request.getCreateDate().getTime();
            
            long dt1 = Long.MAX_VALUE;
            if(firedDate != null)
                dt1 = request.getUpdateDate().getTime() - firedDate.getTime();

            if(Math.abs(dt0) < DELAY || dt1 <= 0)
                state = "New ";
            else
                state = "Revised ";
        }
        else
            state = "N/C ";

        // Get event status
        if(event != null) {
            long now = System.currentTimeMillis();
            if(event instanceof Once) {
                if(event.isEnabled())
                    state += "[enabled] ";
                else if(event.getLastFired() != null) {
                    if(event.getLastFired() == co.getRequest().getUpdateDate())
                        state = "Fired [expired] ";
                }
                else
                    state += "[paused] ";
            }
            else {
                Recurring recurring = (Recurring) event;

                if(!event.isEnabled() && now > recurring.getEnds().getTime()) {
                    if(event.getLastFired() == co.getRequest().getUpdateDate())
                        state = "Fired [expired]";
                    else
                        state += "[expired] ";
                }

                else if(!event.isEnabled() && now < recurring.getEnds().getTime())
                    state += "[paused] ";

                else if(event.isEnabled() && now > recurring.getEnds().getTime()) {
                    if(event.getLastFired() == co.getRequest().getUpdateDate())
                        state = "Fired [expired] ";
                    else
                        state = "[expired] ";
                }

                else if(event.isEnabled() && now < recurring.getEnds().getTime()) {
                    if(event.getLastFired() == co.getRequest().getUpdateDate())
                        state = "Fired [enabled]";
                    else
                        state += "[enabled] ";
                }
            }
        }
        return state;
    }

    public CompositeOrder getRow(int row) {
        if(row < 0 || row >= requests.size())
            return null;

        return requests.get(row);
    }

    public void remove(int row) {
        requests.remove(row);
    }

    public void clear() {
        requests.clear();

        status = WorkRequest.Status.DISPATCHED;
        
        serviceId = -1;
    }

    public void refresh() {
        requests.clear();

        if(status == WorkRequest.Status.PENDING || status == WorkRequest.Status.RECURRING)
            refreshForwards();

        else if(status == WorkRequest.Status.OPEN || status == WorkRequest.Status.CLOSED)
            refreshOpenClose();

        else
            refreshFromWorkSlates();

        Logger.log("retrieved total="+requests.size()+" orders");

        // Sort requests with most recent first
        Collections.sort(requests, new Comparator() {
            public int compare(Object o1, Object o2) {
                Date d1 = ((CompositeOrder) o1).getRequest().getUpdateDate();
                Date d2 = ((CompositeOrder) o2).getRequest().getUpdateDate();
                return d2.compareTo(d1);
            }
        });

        this.fireTableDataChanged();
    }

    /**
     * Refreshes only the one-time pending requests.
     * Note: These aren't anybody's work slateName (yet) so we'll just get
     * them all.
     */
    private void refreshForwards() {
        if(!Helper.isSuperUser(user))
            return;

        String criteria = "/list [";
        criteria += " serviceId = " + serviceId;
        criteria += " and updateDate < " + end;
        criteria += " and updateDate >= " + start;
        criteria += " ]";

        ArrayList<Event> events =
                Helper.query(NetTask.REPOS_FUTURES, criteria, GroundsWorklet.getInstance().getContext());

        for(Event event : events) {
            // Put the event in the cache regardless
            eventsCache.put(event.getId(), event);

            // If the status is recurring but it is a ONCE or if it pending and not a once
            // we don't care
            if(status == WorkRequest.Status.RECURRING && event instanceof Once)
                continue;

            if(status == WorkRequest.Status.PENDING && !(event instanceof Once))
                continue;
            
            // Make a copy of the template so we don't accidentlly
            // write it back to the repository
            GroundsRequest template =
                    (GroundsRequest) Helper.copy(event.getTemplate());

            String slateName = "<none>";

            Integer workSlateId = event.getWorkSlateId();

            if(workSlateId != -1) {
                WorkSlate workSlate = GroundsHelper.getWorkSlate(workSlateId);

                if(workSlate != null)
                    slateName = workSlate.getName();
            }

            // By default the template's update date is the date the
            // event was created. However, to reflect changes in the GUI
            // we will use the last fired date, if it exists.
            
            // WHY not use the event's update date? 'Cause the event gets
            // updated when the server is restarted (ie, the event gets re-scheduled)
            // and this would appear wrong from the user's POV
            WorkDate firedDate = event.getLastFired();

            if(firedDate != null && template.getUpdateDate().getTime() < firedDate.getTime())
                template.setUpdateDate(event.getLastFired());

            if(template.getUpdateDate().getTime() < start ||
                    template.getUpdateDate().getTime() >= end)
                continue;

            // Template's user id, and id are same as the event
            template.setUpdateUserId(event.getUpdateUserId());
            template.setId(event.getId());
            template.setFutureId(event.getId());

            String eventName = null;
            int index = event.getClass().getName().lastIndexOf(".");
            if(index == -1)
                eventName = event.getClass().getName();
            else
                eventName = event.getClass().getName().substring(index+1);

            slateName = slateName + " [" + eventName.toUpperCase() + "] ";

            Date updateDate = new Date(template.getUpdateDate().getTime());
            Date createDate = new Date(template.getCreateDate().getTime());

            requests.add(new CompositeOrder(slateName,template,event));
        }
    }

    /**
     * Refershes only the open / closed work requests
     */
    private void refreshOpenClose() {
        if(!Helper.isSuperUser(user))
            return;
        
        String criteria = "/list [";
        criteria += " serviceId = " + serviceId;
        criteria += " and updateDate < " + end;
        criteria += " and updateDate >= " + start;
        criteria += " and status = '" + status+"'";
        criteria += " ]";

        ArrayList wos = Helper.query(NetTask.REPOS_CLEARINGHOUSE, criteria, GroundsWorklet.getInstance().getContext());
        for(Object wo : wos) {
            GroundsRequest request = (GroundsRequest)wo;

            requests.add(new CompositeOrder("<none>",request));
        }
    }

    /**
     * Refresh only those work requests that are on my workslate(s).
     */
    private void refreshFromWorkSlates() {
        // Retrieve all work requests the user is pointing to
        ArrayList<WorkSlate> slates =
                GroundsHelper.getWorkSlates(user.getWorkSlateList());

        Logger.log("refreshing '"+user+"'"+" with "+user.getWorkSlateList().size()+" slates");

        if(slates.size() == 0)
            return;

        String criteria = "/list [";
        criteria += " serviceId = " + serviceId;
        criteria += " and updateDate <" + end;
        criteria += " and updateDate >= " + start;
        criteria += " and status = '" + status+"'";
        criteria += " ]";

        ArrayList<GroundsRequest> results =
                (ArrayList<GroundsRequest>) Helper.query(NetTask.REPOS_CLEARINGHOUSE, criteria, GroundsWorklet.getInstance().getContext());

        for(GroundsRequest request : results) {
            for(WorkSlate slate : slates) {
                for(Integer id : slate.getWorkRequestList()) {
                    if(request.getId().equals(id))
                        requests.add(new CompositeOrder(slate.getName(),request));
                }
            }
        }
    }

    public void setServiceId(Integer serviceId) {
        this.serviceId = serviceId;
    }

    public void setStatus(WorkRequest.Status status) {
        this.status = status;
    }

    public void setStart(long start) {
        this.start = start;
    }

    public void setEnd(long end) {
        this.end = end;
    }

    public User getUser() {
        return user;
    }
    
    public void setUser(User user) {
        this.user = user;
    }

    private Event getEvent(Integer eventId) {
        // Get the event from the cache
        Event event = (Event) eventsCache.get(eventId);

        if(event != null)
            return event;

        // If the event is NOT in the cache, retrieve it
        // from the repository
        if (event == null)
            event = (Event) Helper.fetch(NetTask.REPOS_FUTURES, eventId, GroundsWorklet.getInstance().getContext());

        // If the event is in the repository, cache it here
        // otherwise, it must have been deleted which is okay
        // as this just means a corresponding request doesn't
        // have it's reference event
        if (event != null)
            eventsCache.put(eventId, event);

        return event;
    }
}
